/*
 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "base/arraysize.h"
#include "base/checks.h"
#include "audio_device/audio_device_config.h"
#include "audio_device/mac/audio_device_mac.h"
#include "audio_device/mac/portaudio/pa_ringbuffer.h"
#include "system_wrappers/interface/event_wrapper.h"
#include "system_wrappers/interface/thread_wrapper.h"
#include "system_wrappers/interface/trace.h"

#include <ApplicationServices/ApplicationServices.h>
#include <libkern/OSAtomic.h>   // OSAtomicCompareAndSwap()
#include <mach/mach.h>          // mach_task_self()
#include <sys/sysctl.h>         // sysctlbyname()



namespace webrtc
{

#define WEBRTC_CA_RETURN_ON_ERR(expr)                   \
  do {                                                  \
    err = expr;                                         \
    if (err != noErr) {                                 \
      logCAMsg(kTraceError, kTraceAudioDevice, _id,     \
               "Error in " #expr, (const char *)&err);  \
      return -1;                                        \
    }                                                   \
  } while(0)

#define WEBRTC_CA_LOG_ERR(expr)                         \
  do {                                                  \
    err = expr;                                         \
    if (err != noErr) {                                 \
      logCAMsg(kTraceError, kTraceAudioDevice, _id,     \
               "Error in " #expr, (const char *)&err);  \
    }                                                   \
  } while(0)

#define WEBRTC_CA_LOG_WARN(expr)                        \
  do {                                                  \
    err = expr;                                         \
    if (err != noErr) {                                 \
      logCAMsg(kTraceWarning, kTraceAudioDevice, _id,   \
               "Error in " #expr, (const char *)&err);  \
    }                                                   \
  } while(0)

  enum
    {
      MaxNumberDevices = 64
    };

  void AudioDeviceMac::AtomicSet32(int32_t* theValue, int32_t newValue)
  {
    while (1)
      {
        int32_t oldValue = *theValue;
        if (OSAtomicCompareAndSwap32Barrier(oldValue, newValue, theValue)
            == true)
          {
            return;
          }
      }
  }

  int32_t AudioDeviceMac::AtomicGet32(int32_t* theValue)
  {
    while (1)
      {
        int32_t value = *theValue;
        if (OSAtomicCompareAndSwap32Barrier(value, value, theValue) == true)
          {
            return value;
          }
      }
  }

  // CoreAudio errors are best interpreted as four character strings.
  void AudioDeviceMac::logCAMsg(const TraceLevel level,
                                const TraceModule module,
                                const int32_t id, const char *msg,
                                const char *err)
  {
    RTC_DCHECK(msg != NULL);
    RTC_DCHECK(err != NULL);

#ifdef WEBRTC_ARCH_BIG_ENDIAN
    WEBRTC_TRACE(level, module, id, "%s: %.4s", msg, err);
#else
    // We need to flip the characters in this case.
    WEBRTC_TRACE(level, module, id, "%s: %.1s%.1s%.1s%.1s", msg, err + 3, err
                 + 2, err + 1, err);
#endif
  }

  AudioDeviceMac::AudioDeviceMac(const int32_t id) :
    _ptrAudioBuffer(NULL),
    _critSect(*CriticalSectionWrapper::CreateCriticalSection()),
    _stopEventRec(*EventWrapper::Create()),
    _stopEvent(*EventWrapper::Create()),
    _id(id),
    _mixerManager(id),
    _inputDeviceIndex(0),
    _outputDeviceIndex(0),
    _inputDeviceID(kAudioObjectUnknown),
    _outputDeviceID(kAudioObjectUnknown),
    _inputDeviceIsSpecified(false),
    _outputDeviceIsSpecified(false),
    _recChannels(N_REC_CHANNELS),
    _playChannels(N_PLAY_CHANNELS),
    _captureBufData(NULL),
    _renderBufData(NULL),
    _playBufType(AudioDeviceModule::kFixedBufferSize),
    _initialized(false),
    _isShutDown(false),
    _recording(false),
    _playing(false),
    _recIsInitialized(false),
    _playIsInitialized(false),
    _AGC(false),
    _renderDeviceIsAlive(1),
    _captureDeviceIsAlive(1),
    _twoDevices(true),
    _doStop(false),
    _doStopRec(false),
    _macBookPro(false),
    _macBookProPanRight(false),
    _captureLatencyUs(0),
    _renderLatencyUs(0),
    _captureDelayUs(0),
    _renderDelayUs(0),
    _renderDelayOffsetSamples(0),
    _playBufDelayFixed(20),
    _playWarning(0),
    _playError(0),
    _recWarning(0),
    _recError(0),
    _paCaptureBuffer(NULL),
    _paRenderBuffer(NULL),
    _captureBufSizeSamples(0),
    _renderBufSizeSamples(0),
    prev_key_state_()
  {
    WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, id,
                 "%s created", __FUNCTION__);

    RTC_DCHECK(&_stopEvent != NULL);
    RTC_DCHECK(&_stopEventRec != NULL);

    memset(_renderConvertData, 0, sizeof(_renderConvertData));
    memset(&_outStreamFormat, 0, sizeof(AudioStreamBasicDescription));
    memset(&_outDesiredFormat, 0, sizeof(AudioStreamBasicDescription));
    memset(&_inStreamFormat, 0, sizeof(AudioStreamBasicDescription));
    memset(&_inDesiredFormat, 0, sizeof(AudioStreamBasicDescription));
  }


  AudioDeviceMac::~AudioDeviceMac()
  {
    WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, _id,
                 "%s destroyed", __FUNCTION__);

    if (!_isShutDown)
      {
        Terminate();
      }

    RTC_DCHECK(!capture_worker_thread_.get());
    RTC_DCHECK(!render_worker_thread_.get());

    if (_paRenderBuffer)
      {
        delete _paRenderBuffer;
        _paRenderBuffer = NULL;
      }

    if (_paCaptureBuffer)
      {
        delete _paCaptureBuffer;
        _paCaptureBuffer = NULL;
      }

    if (_renderBufData)
      {
        delete[] _renderBufData;
        _renderBufData = NULL;
      }

    if (_captureBufData)
      {
        delete[] _captureBufData;
        _captureBufData = NULL;
      }

    kern_return_t kernErr = KERN_SUCCESS;
    kernErr = semaphore_destroy(mach_task_self(), _renderSemaphore);
    if (kernErr != KERN_SUCCESS)
      {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     " semaphore_destroy() error: %d", kernErr);
      }

    kernErr = semaphore_destroy(mach_task_self(), _captureSemaphore);
    if (kernErr != KERN_SUCCESS)
      {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     " semaphore_destroy() error: %d", kernErr);
      }

    delete &_stopEvent;
    delete &_stopEventRec;
    delete &_critSect;
  }

  // ============================================================================
  //                                     API
  // ============================================================================

  void AudioDeviceMac::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer)
  {

    CriticalSectionScoped lock(&_critSect);

    _ptrAudioBuffer = audioBuffer;

    // inform the AudioBuffer about default settings for this implementation
    _ptrAudioBuffer->SetRecordingSampleRate(N_REC_SAMPLES_PER_SEC);
    _ptrAudioBuffer->SetPlayoutSampleRate(N_PLAY_SAMPLES_PER_SEC);
    _ptrAudioBuffer->SetRecordingChannels(N_REC_CHANNELS);
    _ptrAudioBuffer->SetPlayoutChannels(N_PLAY_CHANNELS);
  }

  int32_t AudioDeviceMac::ActiveAudioLayer(
                                           AudioDeviceModule::AudioLayer& audioLayer) const
  {
    audioLayer = AudioDeviceModule::kPlatformDefaultAudio;
    return 0;
  }

  int32_t AudioDeviceMac::Init()
  {

    CriticalSectionScoped lock(&_critSect);

    if (_initialized)
      {
        return 0;
      }

    OSStatus err = noErr;

    _isShutDown = false;

    // PortAudio ring buffers require an elementCount which is a power of two.
    if (_renderBufData == NULL)
      {
        UInt32 powerOfTwo = 1;
        while (powerOfTwo < PLAY_BUF_SIZE_IN_SAMPLES)
          {
            powerOfTwo <<= 1;
          }
        _renderBufSizeSamples = powerOfTwo;
        _renderBufData = new SInt16[_renderBufSizeSamples];
      }

    if (_paRenderBuffer == NULL)
      {
        _paRenderBuffer = new PaUtilRingBuffer;
        PaRingBufferSize bufSize = -1;
        bufSize = PaUtil_InitializeRingBuffer(_paRenderBuffer, sizeof(SInt16),
                                              _renderBufSizeSamples,
                                              _renderBufData);
        if (bufSize == -1)
          {
            WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice,
                         _id, " PaUtil_InitializeRingBuffer() error");
            return -1;
          }
      }

    if (_captureBufData == NULL)
      {
        UInt32 powerOfTwo = 1;
        while (powerOfTwo < REC_BUF_SIZE_IN_SAMPLES)
          {
            powerOfTwo <<= 1;
          }
        _captureBufSizeSamples = powerOfTwo;
        _captureBufData = new Float32[_captureBufSizeSamples];
      }

    if (_paCaptureBuffer == NULL)
      {
        _paCaptureBuffer = new PaUtilRingBuffer;
        PaRingBufferSize bufSize = -1;
        bufSize = PaUtil_InitializeRingBuffer(_paCaptureBuffer,
                                              sizeof(Float32),
                                              _captureBufSizeSamples,
                                              _captureBufData);
        if (bufSize == -1)
          {
            WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice,
                         _id, " PaUtil_InitializeRingBuffer() error");
            return -1;
          }
      }

    kern_return_t kernErr = KERN_SUCCESS;
    kernErr = semaphore_create(mach_task_self(), &_renderSemaphore,
                               SYNC_POLICY_FIFO, 0);
    if (kernErr != KERN_SUCCESS)
      {
        WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id,
                     " semaphore_create() error: %d", kernErr);
        return -1;
      }

    kernErr = semaphore_create(mach_task_self(), &_captureSemaphore,
                               SYNC_POLICY_FIFO, 0);
    if (kernErr != KERN_SUCCESS)
      {
        WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id,
                     " semaphore_create() error: %d", kernErr);
        return -1;
      }

    // Setting RunLoop to NULL here instructs HAL to manage its own thread for
    // notifications. This was the default behaviour on OS X 10.5 and earlier,
    // but now must be explicitly specified. HAL would otherwise try to use the
    // main thread to issue notifications.
    AudioObjectPropertyAddress propertyAddress = {
      kAudioHardwarePropertyRunLoop,
      kAudioObjectPropertyScopeGlobal,
      kAudioObjectPropertyElementMaster };
    CFRunLoopRef runLoop = NULL;
    UInt32 size = sizeof(CFRunLoopRef);
    WEBRTC_CA_RETURN_ON_ERR(AudioObjectSetPropertyData(kAudioObjectSystemObject,
                                                       &propertyAddress, 0, NULL, size, &runLoop));

    // Listen for any device changes.
    propertyAddress.mSelector = kAudioHardwarePropertyDevices;
    WEBRTC_CA_LOG_ERR(AudioObjectAddPropertyListener(kAudioObjectSystemObject,
                                                     &propertyAddress, &objectListenerProc, this));

    // Determine if this is a MacBook Pro
    _macBookPro = false;
    _macBookProPanRight = false;
    char buf[128];
    size_t length = sizeof(buf);
    memset(buf, 0, length);

    int intErr = sysctlbyname("hw.model", buf, &length, NULL, 0);
    if (intErr != 0)
      {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     " Error in sysctlbyname(): %d", err);
      } else
      {
        WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                     " Hardware model: %s", buf);
        if (strncmp(buf, "MacBookPro", 10) == 0)
          {
            _macBookPro = true;
          }
      }

    _playWarning = 0;
    _playError = 0;
    _recWarning = 0;
    _recError = 0;

    _initialized = true;

    return 0;
  }

  int32_t AudioDeviceMac::Terminate()
  {
    printf("terminate\n");
    if (!_initialized)
      {
        return 0;
      }

    if (_recording)
      {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     " Recording must be stopped");
        return -1;
      }

    if (_playing)
      {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     " Playback must be stopped");
        return -1;
      }

    _critSect.Enter();

    _mixerManager.Close();

    OSStatus err = noErr;
    int retVal = 0;

    AudioObjectPropertyAddress propertyAddress = {
      kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal,
      kAudioObjectPropertyElementMaster };
    WEBRTC_CA_LOG_WARN(AudioObjectRemovePropertyListener(kAudioObjectSystemObject,
                                                         &propertyAddress, &objectListenerProc, this));

    err = AudioHardwareUnload();
    if (err != noErr)
      {
        logCAMsg(kTraceError, kTraceAudioDevice, _id,
                 "Error in AudioHardwareUnload()", (const char*) &err);
        retVal = -1;
      }

    _isShutDown = true;
    _initialized = false;
    _outputDeviceIsSpecified = false;
    _inputDeviceIsSpecified = false;

    _critSect.Leave();

    return retVal;
  }

  bool AudioDeviceMac::Initialized() const
  {
    return (_initialized);
  }

  int32_t AudioDeviceMac::SpeakerIsAvailable(bool& available)
  {

    bool wasInitialized = _mixerManager.SpeakerIsInitialized();

    // Make an attempt to open up the
    // output mixer corresponding to the currently selected output device.
    //
    if (!wasInitialized && InitSpeaker() == -1)
      {
        available = false;
        return 0;
      }

    // Given that InitSpeaker was successful, we know that a valid speaker
    // exists.
    available = true;

    // Close the initialized output mixer
    //
    if (!wasInitialized)
      {
        _mixerManager.CloseSpeaker();
      }

    return 0;
  }

  int32_t AudioDeviceMac::InitSpeaker()
  {

    CriticalSectionScoped lock(&_critSect);

    if (_playing)
      {
        return -1;
      }

    if (InitDevice(_outputDeviceIndex, _outputDeviceID, false) == -1)
      {
        return -1;
      }

    if (_inputDeviceID == _outputDeviceID)
      {
        _twoDevices = false;
      } else
      {
        _twoDevices = true;
      }

    if (_mixerManager.OpenSpeaker(_outputDeviceID) == -1)
      {
        return -1;
      }

    return 0;
  }

  int32_t AudioDeviceMac::MicrophoneIsAvailable(bool& available)
  {

    bool wasInitialized = _mixerManager.MicrophoneIsInitialized();

    // Make an attempt to open up the
    // input mixer corresponding to the currently selected output device.
    //
    if (!wasInitialized && InitMicrophone() == -1)
      {
        available = false;
        return 0;
      }

    // Given that InitMicrophone was successful, we know that a valid microphone
    // exists.
    available = true;

    // Close the initialized input mixer
    //
    if (!wasInitialized)
      {
        _mixerManager.CloseMicrophone();
      }

    return 0;
  }

  int32_t AudioDeviceMac::InitMicrophone()
  {

    CriticalSectionScoped lock(&_critSect);

    if (_recording)
      {
        return -1;
      }

    if (InitDevice(_inputDeviceIndex, _inputDeviceID, true) == -1)
      {
        return -1;
      }

    if (_inputDeviceID == _outputDeviceID)
      {
        _twoDevices = false;
      } else
      {
        _twoDevices = true;
      }

    if (_mixerManager.OpenMicrophone(_inputDeviceID) == -1)
      {
        return -1;
      }

    return 0;
  }

  bool AudioDeviceMac::SpeakerIsInitialized() const
  {
    return (_mixerManager.SpeakerIsInitialized());
  }

  bool AudioDeviceMac::MicrophoneIsInitialized() const
  {
    return (_mixerManager.MicrophoneIsInitialized());
  }

  int32_t AudioDeviceMac::SpeakerVolumeIsAvailable(bool& available)
  {

    bool wasInitialized = _mixerManager.SpeakerIsInitialized();

    // Make an attempt to open up the
    // output mixer corresponding to the currently selected output device.
    //
    if (!wasInitialized && InitSpeaker() == -1)
      {
        // If we end up here it means that the selected speaker has no volume
        // control.
        available = false;
        return 0;
      }

    // Given that InitSpeaker was successful, we know that a volume control exists
    //
    available = true;

    // Close the initialized output mixer
    //
    if (!wasInitialized)
      {
        _mixerManager.CloseSpeaker();
      }

    return 0;
  }

  int32_t AudioDeviceMac::SetSpeakerVolume(uint32_t volume)
  {

    return (_mixerManager.SetSpeakerVolume(volume));
  }

  int32_t AudioDeviceMac::SpeakerVolume(uint32_t& volume) const
  {

    uint32_t level(0);

    if (_mixerManager.SpeakerVolume(level) == -1)
      {
        return -1;
      }

    volume = level;
    return 0;
  }

  int32_t AudioDeviceMac::SetWaveOutVolume(uint16_t volumeLeft,
                                           uint16_t volumeRight)
  {

    WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                 "  API call not supported on this platform");
    return -1;
  }

  int32_t
  AudioDeviceMac::WaveOutVolume(uint16_t& /*volumeLeft*/,
                                uint16_t& /*volumeRight*/) const
  {

    WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                 "  API call not supported on this platform");
    return -1;
  }

  int32_t AudioDeviceMac::MaxSpeakerVolume(uint32_t& maxVolume) const
  {

    uint32_t maxVol(0);

    if (_mixerManager.MaxSpeakerVolume(maxVol) == -1)
      {
        return -1;
      }

    maxVolume = maxVol;
    return 0;
  }

  int32_t AudioDeviceMac::MinSpeakerVolume(uint32_t& minVolume) const
  {

    uint32_t minVol(0);

    if (_mixerManager.MinSpeakerVolume(minVol) == -1)
      {
        return -1;
      }

    minVolume = minVol;
    return 0;
  }

  int32_t
  AudioDeviceMac::SpeakerVolumeStepSize(uint16_t& stepSize) const
  {

    uint16_t delta(0);

    if (_mixerManager.SpeakerVolumeStepSize(delta) == -1)
      {
        return -1;
      }

    stepSize = delta;
    return 0;
  }

  int32_t AudioDeviceMac::SpeakerMuteIsAvailable(bool& available)
  {

    bool isAvailable(false);
    bool wasInitialized = _mixerManager.SpeakerIsInitialized();

    // Make an attempt to open up the
    // output mixer corresponding to the currently selected output device.
    //
    if (!wasInitialized && InitSpeaker() == -1)
      {
        // If we end up here it means that the selected speaker has no volume
        // control, hence it is safe to state that there is no mute control
        // already at this stage.
        available = false;
        return 0;
      }

    // Check if the selected speaker has a mute control
    //
    _mixerManager.SpeakerMuteIsAvailable(isAvailable);

    available = isAvailable;

    // Close the initialized output mixer
    //
    if (!wasInitialized)
      {
        _mixerManager.CloseSpeaker();
      }

    return 0;
  }

  int32_t AudioDeviceMac::SetSpeakerMute(bool enable)
  {
    return (_mixerManager.SetSpeakerMute(enable));
  }

  int32_t AudioDeviceMac::SpeakerMute(bool& enabled) const
  {

    bool muted(0);

    if (_mixerManager.SpeakerMute(muted) == -1)
      {
        return -1;
      }

    enabled = muted;
    return 0;
  }

  int32_t AudioDeviceMac::MicrophoneMuteIsAvailable(bool& available)
  {

    bool isAvailable(false);
    bool wasInitialized = _mixerManager.MicrophoneIsInitialized();

    // Make an attempt to open up the
    // input mixer corresponding to the currently selected input device.
    //
    if (!wasInitialized && InitMicrophone() == -1)
      {
        // If we end up here it means that the selected microphone has no volume
        // control, hence it is safe to state that there is no boost control
        // already at this stage.
        available = false;
        return 0;
      }

    // Check if the selected microphone has a mute control
    //
    _mixerManager.MicrophoneMuteIsAvailable(isAvailable);
    available = isAvailable;

    // Close the initialized input mixer
    //
    if (!wasInitialized)
      {
        _mixerManager.CloseMicrophone();
      }

    return 0;
  }

  int32_t AudioDeviceMac::SetMicrophoneMute(bool enable)
  {
    return (_mixerManager.SetMicrophoneMute(enable));
  }

  int32_t AudioDeviceMac::MicrophoneMute(bool& enabled) const
  {

    bool muted(0);

    if (_mixerManager.MicrophoneMute(muted) == -1)
      {
        return -1;
      }

    enabled = muted;
    return 0;
  }

  int32_t AudioDeviceMac::MicrophoneBoostIsAvailable(bool& available)
  {

    bool isAvailable(false);
    bool wasInitialized = _mixerManager.MicrophoneIsInitialized();

    // Enumerate all avaliable microphone and make an attempt to open up the
    // input mixer corresponding to the currently selected input device.
    //
    if (!wasInitialized && InitMicrophone() == -1)
      {
        // If we end up here it means that the selected microphone has no volume
        // control, hence it is safe to state that there is no boost control
        // already at this stage.
        available = false;
        return 0;
      }

    // Check if the selected microphone has a boost control
    //
    _mixerManager.MicrophoneBoostIsAvailable(isAvailable);
    available = isAvailable;

    // Close the initialized input mixer
    //
    if (!wasInitialized)
      {
        _mixerManager.CloseMicrophone();
      }

    return 0;
  }

  int32_t AudioDeviceMac::SetMicrophoneBoost(bool enable)
  {

    return (_mixerManager.SetMicrophoneBoost(enable));
  }

  int32_t AudioDeviceMac::MicrophoneBoost(bool& enabled) const
  {

    bool onOff(0);

    if (_mixerManager.MicrophoneBoost(onOff) == -1)
      {
        return -1;
      }

    enabled = onOff;
    return 0;
  }

  int32_t AudioDeviceMac::StereoRecordingIsAvailable(bool& available)
  {

    bool isAvailable(false);
    bool wasInitialized = _mixerManager.MicrophoneIsInitialized();

    if (!wasInitialized && InitMicrophone() == -1)
      {
        // Cannot open the specified device
        available = false;
        return 0;
      }

    // Check if the selected microphone can record stereo
    //
    _mixerManager.StereoRecordingIsAvailable(isAvailable);
    available = isAvailable;

    // Close the initialized input mixer
    //
    if (!wasInitialized)
      {
        _mixerManager.CloseMicrophone();
      }

    return 0;
  }

  int32_t AudioDeviceMac::SetStereoRecording(bool enable)
  {

    if (enable)
      _recChannels = 2;
    else
      _recChannels = 1;

    return 0;
  }

  int32_t AudioDeviceMac::StereoRecording(bool& enabled) const
  {

    if (_recChannels == 2)
      enabled = true;
    else
      enabled = false;

    return 0;
  }

  int32_t AudioDeviceMac::StereoPlayoutIsAvailable(bool& available)
  {

    bool isAvailable(false);
    bool wasInitialized = _mixerManager.SpeakerIsInitialized();

    if (!wasInitialized && InitSpeaker() == -1)
      {
        // Cannot open the specified device
        available = false;
        return 0;
      }

    // Check if the selected microphone can record stereo
    //
    _mixerManager.StereoPlayoutIsAvailable(isAvailable);
    available = isAvailable;

    // Close the initialized input mixer
    //
    if (!wasInitialized)
      {
        _mixerManager.CloseSpeaker();
      }

    return 0;
  }

  int32_t AudioDeviceMac::SetStereoPlayout(bool enable)
  {

    if (enable)
      _playChannels = 2;
    else
      _playChannels = 1;

    return 0;
  }

  int32_t AudioDeviceMac::StereoPlayout(bool& enabled) const
  {

    if (_playChannels == 2)
      enabled = true;
    else
      enabled = false;

    return 0;
  }

  int32_t AudioDeviceMac::SetAGC(bool enable)
  {

    _AGC = enable;

    return 0;
  }

  bool AudioDeviceMac::AGC() const
  {

    return _AGC;
  }

  int32_t AudioDeviceMac::MicrophoneVolumeIsAvailable(bool& available)
  {

    bool wasInitialized = _mixerManager.MicrophoneIsInitialized();

    // Make an attempt to open up the
    // input mixer corresponding to the currently selected output device.
    //
    if (!wasInitialized && InitMicrophone() == -1)
      {
        // If we end up here it means that the selected microphone has no volume
        // control.
        available = false;
        return 0;
      }

    // Given that InitMicrophone was successful, we know that a volume control
    // exists
    //
    available = true;

    // Close the initialized input mixer
    //
    if (!wasInitialized)
      {
        _mixerManager.CloseMicrophone();
      }

    return 0;
  }

  int32_t AudioDeviceMac::SetMicrophoneVolume(uint32_t volume)
  {

    return (_mixerManager.SetMicrophoneVolume(volume));
  }

  int32_t AudioDeviceMac::MicrophoneVolume(uint32_t& volume) const
  {

    uint32_t level(0);

    if (_mixerManager.MicrophoneVolume(level) == -1)
      {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  failed to retrive current microphone level");
        return -1;
      }

    volume = level;
    return 0;
  }

  int32_t
  AudioDeviceMac::MaxMicrophoneVolume(uint32_t& maxVolume) const
  {

    uint32_t maxVol(0);

    if (_mixerManager.MaxMicrophoneVolume(maxVol) == -1)
      {
        return -1;
      }

    maxVolume = maxVol;
    return 0;
  }

  int32_t
  AudioDeviceMac::MinMicrophoneVolume(uint32_t& minVolume) const
  {

    uint32_t minVol(0);

    if (_mixerManager.MinMicrophoneVolume(minVol) == -1)
      {
        return -1;
      }

    minVolume = minVol;
    return 0;
  }

  int32_t
  AudioDeviceMac::MicrophoneVolumeStepSize(uint16_t& stepSize) const
  {

    uint16_t delta(0);

    if (_mixerManager.MicrophoneVolumeStepSize(delta) == -1)
      {
        return -1;
      }

    stepSize = delta;
    return 0;
  }

  int16_t AudioDeviceMac::PlayoutDevices()
  {

    AudioDeviceID playDevices[MaxNumberDevices];
    return GetNumberDevices(kAudioDevicePropertyScopeOutput, playDevices,
                            MaxNumberDevices);
  }

  int32_t AudioDeviceMac::SetPlayoutDevice(uint16_t index)
  {
    CriticalSectionScoped lock(&_critSect);

    if (_playIsInitialized)
      {
        return -1;
      }

    AudioDeviceID playDevices[MaxNumberDevices];
    uint32_t nDevices = GetNumberDevices(kAudioDevicePropertyScopeOutput,
                                         playDevices, MaxNumberDevices);
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "  number of availiable waveform-audio output devices is %u",
                 nDevices);

    if (index > (nDevices - 1))
      {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "  device index is out of range [0,%u]", (nDevices - 1));
        return -1;
      }

    _outputDeviceIndex = index;
    _outputDeviceIsSpecified = true;

    return 0;
  }

  int32_t AudioDeviceMac::SetPlayoutDevice(
                                           AudioDeviceModule::WindowsDeviceType /*device*/)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "WindowsDeviceType not supported");
    return -1;
  }

  int32_t AudioDeviceMac::PlayoutDeviceName(
                                            uint16_t index,
                                            char name[kAdmMaxDeviceNameSize],
                                            char guid[kAdmMaxGuidSize])
  {

    const uint16_t nDevices(PlayoutDevices());

    if ((index > (nDevices - 1)) || (name == NULL))
      {
        return -1;
      }

    memset(name, 0, kAdmMaxDeviceNameSize);

    if (guid != NULL)
      {
        memset(guid, 0, kAdmMaxGuidSize);
      }

    return GetDeviceName(kAudioDevicePropertyScopeOutput, index, name);
  }

  int32_t AudioDeviceMac::RecordingDeviceName(
                                              uint16_t index,
                                              char name[kAdmMaxDeviceNameSize],
                                              char guid[kAdmMaxGuidSize])
  {

    const uint16_t nDevices(RecordingDevices());

    if ((index > (nDevices - 1)) || (name == NULL))
      {
        return -1;
      }

    memset(name, 0, kAdmMaxDeviceNameSize);

    if (guid != NULL)
      {
        memset(guid, 0, kAdmMaxGuidSize);
      }

    return GetDeviceName(kAudioDevicePropertyScopeInput, index, name);
  }

  int16_t AudioDeviceMac::RecordingDevices()
  {

    AudioDeviceID recDevices[MaxNumberDevices];
    return GetNumberDevices(kAudioDevicePropertyScopeInput, recDevices,
                            MaxNumberDevices);
  }

  int32_t AudioDeviceMac::SetRecordingDevice(uint16_t index)
  {

    if (_recIsInitialized)
      {
        return -1;
      }

    AudioDeviceID recDevices[MaxNumberDevices];
    uint32_t nDevices = GetNumberDevices(kAudioDevicePropertyScopeInput,
                                         recDevices, MaxNumberDevices);
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "  number of availiable waveform-audio input devices is %u",
                 nDevices);

    if (index > (nDevices - 1))
      {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "  device index is out of range [0,%u]", (nDevices - 1));
        printf("device index out of range\n");
        return -1;
      }

    _inputDeviceIndex = index;
    _inputDeviceIsSpecified = true;

    return 0;
  }


  int32_t
  AudioDeviceMac::SetRecordingDevice(AudioDeviceModule::WindowsDeviceType /*device*/)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "WindowsDeviceType not supported");
    return -1;
  }

  int32_t AudioDeviceMac::PlayoutIsAvailable(bool& available)
  {

    available = true;

    // Try to initialize the playout side
    if (InitPlayout() == -1)
      {
        available = false;
      }

    // We destroy the IOProc created by InitPlayout() in implDeviceIOProc().
    // We must actually start playout here in order to have the IOProc
    // deleted by calling StopPlayout().
    if (StartPlayout() == -1)
      {
        available = false;
      }

    // Cancel effect of initialization
    if (StopPlayout() == -1)
      {
        available = false;
      }

    return 0;
  }

  int32_t AudioDeviceMac::RecordingIsAvailable(bool& available)
  {

    available = true;

    // Try to initialize the recording side
    if (InitRecording() == -1)
      {
        available = false;
      }

    // We destroy the IOProc created by InitRecording() in implInDeviceIOProc().
    // We must actually start recording here in order to have the IOProc
    // deleted by calling StopRecording().
    if (StartRecording() == -1)
      {
        available = false;
      }

    // Cancel effect of initialization
    if (StopRecording() == -1)
      {
        available = false;
      }

    return 0;
  }

  int32_t AudioDeviceMac::InitPlayout()
  {
    CriticalSectionScoped lock(&_critSect);

    if (_playing)
      {
        return -1;
      }

    if (!_outputDeviceIsSpecified)
      {
        return -1;
      }

    if (_playIsInitialized)
      {
        return 0;
      }

    // Initialize the speaker (devices might have been added or removed)
    if (InitSpeaker() == -1)
      {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  InitSpeaker() failed");
      }

    if (!MicrophoneIsInitialized())
      {
        // Make this call to check if we are using
        // one or two devices (_twoDevices)
        bool available = false;
        if (MicrophoneIsAvailable(available) == -1)
          {
            WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                         "  MicrophoneIsAvailable() failed");
          }
      }

    PaUtil_FlushRingBuffer(_paRenderBuffer);

    OSStatus err = noErr;
    UInt32 size = 0;
    _renderDelayOffsetSamples = 0;
    _renderDelayUs = 0;
    _renderLatencyUs = 0;
    _renderDeviceIsAlive = 1;
    _doStop = false;

    // The internal microphone of a MacBook Pro is located under the left speaker
    // grille. When the internal speakers are in use, we want to fully stereo
    // pan to the right.
    AudioObjectPropertyAddress
      propertyAddress = { kAudioDevicePropertyDataSource,
                          kAudioDevicePropertyScopeOutput, 0 };
    if (_macBookPro)
      {
        _macBookProPanRight = false;
        Boolean hasProperty = AudioObjectHasProperty(_outputDeviceID,
                                                     &propertyAddress);
        if (hasProperty)
          {
            UInt32 dataSource = 0;
            size = sizeof(dataSource);
            WEBRTC_CA_LOG_WARN(AudioObjectGetPropertyData(_outputDeviceID,
                                                          &propertyAddress, 0, NULL, &size, &dataSource));

            if (dataSource == 'ispk')
              {
                _macBookProPanRight = true;
                WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice,
                             _id,
                             "MacBook Pro using internal speakers; stereo"
                             " panning right");
              } else
              {
                WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice,
                             _id, "MacBook Pro not using internal speakers");
              }

            // Add a listener to determine if the status changes.
            WEBRTC_CA_LOG_WARN(AudioObjectAddPropertyListener(_outputDeviceID,
                                                              &propertyAddress, &objectListenerProc, this));
          }
      }

    // Get current stream description
    propertyAddress.mSelector = kAudioDevicePropertyStreamFormat;
    memset(&_outStreamFormat, 0, sizeof(_outStreamFormat));
    size = sizeof(_outStreamFormat);
    WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(_outputDeviceID,
                                                       &propertyAddress, 0, NULL, &size, &_outStreamFormat));

    if (_outStreamFormat.mFormatID != kAudioFormatLinearPCM)
      {
        logCAMsg(kTraceError, kTraceAudioDevice, _id,
                 "Unacceptable output stream format -> mFormatID",
                 (const char *) &_outStreamFormat.mFormatID);
        return -1;
      }

    if (_outStreamFormat.mChannelsPerFrame > N_DEVICE_CHANNELS)
      {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "Too many channels on output device (mChannelsPerFrame = %d)",
                     _outStreamFormat.mChannelsPerFrame);
        return -1;
      }

    if (_outStreamFormat.mFormatFlags & kAudioFormatFlagIsNonInterleaved)
      {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "Non-interleaved audio data is not supported.",
                     "AudioHardware streams should not have this format.");
        return -1;
      }

    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "Ouput stream format:");
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "mSampleRate = %f, mChannelsPerFrame = %u",
                 _outStreamFormat.mSampleRate,
                 _outStreamFormat.mChannelsPerFrame);
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "mBytesPerPacket = %u, mFramesPerPacket = %u",
                 _outStreamFormat.mBytesPerPacket,
                 _outStreamFormat.mFramesPerPacket);
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "mBytesPerFrame = %u, mBitsPerChannel = %u",
                 _outStreamFormat.mBytesPerFrame,
                 _outStreamFormat.mBitsPerChannel);
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "mFormatFlags = %u",
                 _outStreamFormat.mFormatFlags);
    logCAMsg(kTraceInfo, kTraceAudioDevice, _id, "mFormatID",
             (const char *) &_outStreamFormat.mFormatID);

    // Our preferred format to work with.
    if (_outStreamFormat.mChannelsPerFrame < 2)
      {
        // Disable stereo playout when we only have one channel on the device.
        _playChannels = 1;
        WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                     "Stereo playout unavailable on this device");
      }
    WEBRTC_CA_RETURN_ON_ERR(SetDesiredPlayoutFormat());

    // Listen for format changes.
    propertyAddress.mSelector = kAudioDevicePropertyStreamFormat;
    WEBRTC_CA_RETURN_ON_ERR(AudioObjectAddPropertyListener(_outputDeviceID,
                                                           &propertyAddress,
                                                           &objectListenerProc,
                                                           this));

    // Listen for processor overloads.
    propertyAddress.mSelector = kAudioDeviceProcessorOverload;
    WEBRTC_CA_LOG_WARN(AudioObjectAddPropertyListener(_outputDeviceID,
                                                      &propertyAddress,
                                                      &objectListenerProc,
                                                      this));

    if (_twoDevices || !_recIsInitialized)
      {
        WEBRTC_CA_RETURN_ON_ERR(AudioDeviceCreateIOProcID(_outputDeviceID,
                                                          deviceIOProc,
                                                          this,
                                                          &_deviceIOProcID));
      }

    _playIsInitialized = true;

    return 0;
  }

  int32_t AudioDeviceMac::InitRecording()
  {

    CriticalSectionScoped lock(&_critSect);

    if (_recording)
      {
        return -1;
      }

    if (!_inputDeviceIsSpecified)
      {
        return -1;
      }

    if (_recIsInitialized)
      {
        return 0;
      }

    // Initialize the microphone (devices might have been added or removed)
    if (InitMicrophone() == -1)
      {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "  InitMicrophone() failed");
      }

    if (!SpeakerIsInitialized())
      {
        // Make this call to check if we are using
        // one or two devices (_twoDevices)
        bool available = false;
        if (SpeakerIsAvailable(available) == -1)
          {
            WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                         "  SpeakerIsAvailable() failed");
          }
      }

    OSStatus err = noErr;
    UInt32 size = 0;

    PaUtil_FlushRingBuffer(_paCaptureBuffer);

    _captureDelayUs = 0;
    _captureLatencyUs = 0;
    _captureDeviceIsAlive = 1;
    _doStopRec = false;

    // Get current stream description
    AudioObjectPropertyAddress
      propertyAddress = { kAudioDevicePropertyStreamFormat,
                          kAudioDevicePropertyScopeInput, 0 };
    memset(&_inStreamFormat, 0, sizeof(_inStreamFormat));
    size = sizeof(_inStreamFormat);
    WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(_inputDeviceID,
                                                       &propertyAddress, 0, NULL, &size, &_inStreamFormat));

    if (_inStreamFormat.mFormatID != kAudioFormatLinearPCM)
      {
        logCAMsg(kTraceError, kTraceAudioDevice, _id,
                 "Unacceptable input stream format -> mFormatID",
                 (const char *) &_inStreamFormat.mFormatID);
        return -1;
      }

    if (_inStreamFormat.mChannelsPerFrame > N_DEVICE_CHANNELS)
      {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "Too many channels on input device (mChannelsPerFrame = %d)",
                     _inStreamFormat.mChannelsPerFrame);
        return -1;
      }

    const int io_block_size_samples = _inStreamFormat.mChannelsPerFrame *
      _inStreamFormat.mSampleRate / 100 * N_BLOCKS_IO;
    if (io_block_size_samples > _captureBufSizeSamples)
      {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "Input IO block size (%d) is larger than ring buffer (%u)",
                     io_block_size_samples, _captureBufSizeSamples);
        return -1;
      }

    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 " Input stream format:");
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 " mSampleRate = %f, mChannelsPerFrame = %u",
                 _inStreamFormat.mSampleRate, _inStreamFormat.mChannelsPerFrame);
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 " mBytesPerPacket = %u, mFramesPerPacket = %u",
                 _inStreamFormat.mBytesPerPacket,
                 _inStreamFormat.mFramesPerPacket);
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 " mBytesPerFrame = %u, mBitsPerChannel = %u",
                 _inStreamFormat.mBytesPerFrame,
                 _inStreamFormat.mBitsPerChannel);
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 " mFormatFlags = %u",
                 _inStreamFormat.mFormatFlags);
    logCAMsg(kTraceInfo, kTraceAudioDevice, _id, "mFormatID",
             (const char *) &_inStreamFormat.mFormatID);

    // Our preferred format to work with
    if (_inStreamFormat.mChannelsPerFrame >= 2 && (_recChannels == 2))
      {
        _inDesiredFormat.mChannelsPerFrame = 2;
      } else
      {
        // Disable stereo recording when we only have one channel on the device.
        _inDesiredFormat.mChannelsPerFrame = 1;
        _recChannels = 1;
        WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                     "Stereo recording unavailable on this device");
      }

    if (_ptrAudioBuffer)
      {
        // Update audio buffer with the selected parameters
        _ptrAudioBuffer->SetRecordingSampleRate(N_REC_SAMPLES_PER_SEC);
        _ptrAudioBuffer->SetRecordingChannels((uint8_t) _recChannels);
      }

    _inDesiredFormat.mSampleRate = N_REC_SAMPLES_PER_SEC;
    _inDesiredFormat.mBytesPerPacket = _inDesiredFormat.mChannelsPerFrame
      * sizeof(SInt16);
    _inDesiredFormat.mFramesPerPacket = 1;
    _inDesiredFormat.mBytesPerFrame = _inDesiredFormat.mChannelsPerFrame
      * sizeof(SInt16);
    _inDesiredFormat.mBitsPerChannel = sizeof(SInt16) * 8;

    _inDesiredFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger
      | kLinearPCMFormatFlagIsPacked;
#ifdef WEBRTC_ARCH_BIG_ENDIAN
    _inDesiredFormat.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
#endif
    _inDesiredFormat.mFormatID = kAudioFormatLinearPCM;

    WEBRTC_CA_RETURN_ON_ERR(AudioConverterNew(&_inStreamFormat, &_inDesiredFormat,
                                              &_captureConverter));

    // First try to set buffer size to desired value (10 ms * N_BLOCKS_IO)
    // TODO(xians): investigate this block.
    UInt32 bufByteCount = (UInt32)((_inStreamFormat.mSampleRate / 1000.0)
                                   * 10.0 * N_BLOCKS_IO * _inStreamFormat.mChannelsPerFrame
                                   * sizeof(Float32));
    if (_inStreamFormat.mFramesPerPacket != 0)
      {
        if (bufByteCount % _inStreamFormat.mFramesPerPacket != 0)
          {
            bufByteCount = ((UInt32)(bufByteCount
                                     / _inStreamFormat.mFramesPerPacket) + 1)
              * _inStreamFormat.mFramesPerPacket;
          }
      }

    // Ensure the buffer size is within the acceptable range provided by the device.
    propertyAddress.mSelector = kAudioDevicePropertyBufferSizeRange;
    AudioValueRange range;
    size = sizeof(range);
    WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(_inputDeviceID,
                                                       &propertyAddress, 0, NULL, &size, &range));
    if (range.mMinimum > bufByteCount)
      {
        bufByteCount = range.mMinimum;
      } else if (range.mMaximum < bufByteCount)
      {
        bufByteCount = range.mMaximum;
      }

    propertyAddress.mSelector = kAudioDevicePropertyBufferSize;
    size = sizeof(bufByteCount);
    WEBRTC_CA_RETURN_ON_ERR(AudioObjectSetPropertyData(_inputDeviceID,
                                                       &propertyAddress, 0, NULL, size, &bufByteCount));

    // Get capture device latency
    propertyAddress.mSelector = kAudioDevicePropertyLatency;
    UInt32 latency = 0;
    size = sizeof(UInt32);
    WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(_inputDeviceID,
                                                       &propertyAddress, 0, NULL, &size, &latency));
    _captureLatencyUs = (UInt32)((1.0e6 * latency)
                                 / _inStreamFormat.mSampleRate);

    // Get capture stream latency
    propertyAddress.mSelector = kAudioDevicePropertyStreams;
    AudioStreamID stream = 0;
    size = sizeof(AudioStreamID);
    WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(_inputDeviceID,
                                                       &propertyAddress, 0, NULL, &size, &stream));
    propertyAddress.mSelector = kAudioStreamPropertyLatency;
    size = sizeof(UInt32);
    latency = 0;
    WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(_inputDeviceID,
                                                       &propertyAddress, 0, NULL, &size, &latency));
    _captureLatencyUs += (UInt32)((1.0e6 * latency)
                                  / _inStreamFormat.mSampleRate);

    // Listen for format changes
    // TODO(xians): should we be using kAudioDevicePropertyDeviceHasChanged?
    propertyAddress.mSelector = kAudioDevicePropertyStreamFormat;
    WEBRTC_CA_RETURN_ON_ERR(AudioObjectAddPropertyListener(_inputDeviceID,
                                                           &propertyAddress, &objectListenerProc, this));

    // Listen for processor overloads
    propertyAddress.mSelector = kAudioDeviceProcessorOverload;
    WEBRTC_CA_LOG_WARN(AudioObjectAddPropertyListener(_inputDeviceID,
                                                      &propertyAddress, &objectListenerProc, this));

    if (_twoDevices)
      {
        WEBRTC_CA_RETURN_ON_ERR(AudioDeviceCreateIOProcID(_inputDeviceID,
                                                          inDeviceIOProc, this, &_inDeviceIOProcID));
      } else if (!_playIsInitialized)
      {
        WEBRTC_CA_RETURN_ON_ERR(AudioDeviceCreateIOProcID(_inputDeviceID,
                                                          deviceIOProc, this, &_deviceIOProcID));
      }

    // Mark recording side as initialized
    _recIsInitialized = true;

    return 0;
  }

  int32_t AudioDeviceMac::StartRecording()
  {

    CriticalSectionScoped lock(&_critSect);

    if (!_recIsInitialized)
      {
        return -1;
      }

    if (_recording)
      {
        return 0;
      }

    if (!_initialized)
      {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     " Recording worker thread has not been started");
        return -1;
      }

    RTC_DCHECK(!capture_worker_thread_.get());
    capture_worker_thread_ =
      ThreadWrapper::CreateThread(RunCapture, this, "CaptureWorkerThread");
    printf("capture_worker_thread_ created\n");
    RTC_DCHECK(capture_worker_thread_.get());
    capture_worker_thread_->Start();
    capture_worker_thread_->SetPriority(kRealtimePriority);

    OSStatus err = noErr;
    if (_twoDevices)
      {
        WEBRTC_CA_RETURN_ON_ERR(AudioDeviceStart(_inputDeviceID, _inDeviceIOProcID));
      } else if (!_playing)
      {
        WEBRTC_CA_RETURN_ON_ERR(AudioDeviceStart(_inputDeviceID, _deviceIOProcID));
      }

    _recording = true;

    return 0;
  }

  int32_t AudioDeviceMac::StopRecording()
  {

    CriticalSectionScoped lock(&_critSect);

    if (!_recIsInitialized)
      {
        return 0;
      }
    printf("stop recording\n");
    OSStatus err = noErr;

    // Stop device
    int32_t captureDeviceIsAlive = AtomicGet32(&_captureDeviceIsAlive);
    if (_twoDevices)
      {
        if (_recording && captureDeviceIsAlive == 1)
          {
            _recording = false;
            _doStopRec = true; // Signal to io proc to stop audio device
            _critSect.Leave(); // Cannot be under lock, risk of deadlock
            if (kEventTimeout == _stopEventRec.Wait(2000))
              {
                CriticalSectionScoped critScoped(&_critSect);
                WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                             " Timed out stopping the capture IOProc. "
                             "We may have failed to detect a device removal.");

                WEBRTC_CA_LOG_WARN(AudioDeviceStop(_inputDeviceID,
                                                   _inDeviceIOProcID));
                WEBRTC_CA_LOG_WARN(
                                   AudioDeviceDestroyIOProcID(_inputDeviceID,
                                                              _inDeviceIOProcID));
              }
            _critSect.Enter();
            _doStopRec = false;
            WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id,
                         " Recording stopped");
          }
      }
    else
      {
        // We signal a stop for a shared device even when rendering has
        // not yet ended. This is to ensure the IOProc will return early as
        // intended (by checking |_recording|) before accessing
        // resources we free below (e.g. the capture converter).
        //
        // In the case of a shared devcie, the IOProc will verify
        // rendering has ended before stopping itself.
        if (_recording && captureDeviceIsAlive == 1)
          {
            _recording = false;
            _doStop = true; // Signal to io proc to stop audio device
            _critSect.Leave(); // Cannot be under lock, risk of deadlock
            if (kEventTimeout == _stopEvent.Wait(2000))
              {
                CriticalSectionScoped critScoped(&_critSect);
                WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                             " Timed out stopping the shared IOProc. "
                             "We may have failed to detect a device removal.");

                // We assume rendering on a shared device has stopped as well if
                // the IOProc times out.
                WEBRTC_CA_LOG_WARN(AudioDeviceStop(_outputDeviceID,
                                                   _deviceIOProcID));
                WEBRTC_CA_LOG_WARN(AudioDeviceDestroyIOProcID(_outputDeviceID,
                                                              _deviceIOProcID));
              }
            _critSect.Enter();
            _doStop = false;
            WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id,
                         " Recording stopped (shared)");
          }
      }

    // Setting this signal will allow the worker thread to be stopped.
    AtomicSet32(&_captureDeviceIsAlive, 0);

    if (capture_worker_thread_.get()) {
      _critSect.Leave();
      capture_worker_thread_->Stop();
      capture_worker_thread_.reset();
      _critSect.Enter();
    }

    WEBRTC_CA_LOG_WARN(AudioConverterDispose(_captureConverter));

    // Remove listeners.
    AudioObjectPropertyAddress
      propertyAddress = { kAudioDevicePropertyStreamFormat,
                          kAudioDevicePropertyScopeInput, 0 };
    WEBRTC_CA_LOG_WARN(AudioObjectRemovePropertyListener(_inputDeviceID,
                                                         &propertyAddress, &objectListenerProc, this));

    propertyAddress.mSelector = kAudioDeviceProcessorOverload;
    WEBRTC_CA_LOG_WARN(AudioObjectRemovePropertyListener(_inputDeviceID,
                                                         &propertyAddress, &objectListenerProc, this));

    _recIsInitialized = false;
    _recording = false;

    return 0;
  }

  bool AudioDeviceMac::RecordingIsInitialized() const
  {
    return (_recIsInitialized);
  }

  bool AudioDeviceMac::Recording() const
  {
    return (_recording);
  }

  bool AudioDeviceMac::PlayoutIsInitialized() const
  {
    return (_playIsInitialized);
  }

  int32_t AudioDeviceMac::StartPlayout()
  {

    CriticalSectionScoped lock(&_critSect);

    if (!_playIsInitialized)
      {
        return -1;
      }

    if (_playing)
      {
        return 0;
      }

    RTC_DCHECK(!render_worker_thread_.get());
    render_worker_thread_ =
      ThreadWrapper::CreateThread(RunRender, this, "RenderWorkerThread");
    render_worker_thread_->Start();
    render_worker_thread_->SetPriority(kRealtimePriority);

    if (_twoDevices || !_recording)
      {
        OSStatus err = noErr;
        WEBRTC_CA_RETURN_ON_ERR(AudioDeviceStart(_outputDeviceID, _deviceIOProcID));
      }
    _playing = true;

    return 0;
  }

  int32_t AudioDeviceMac::StopPlayout()
  {

    CriticalSectionScoped lock(&_critSect);

    if (!_playIsInitialized)
      {
        return 0;
      }

    OSStatus err = noErr;

    int32_t renderDeviceIsAlive = AtomicGet32(&_renderDeviceIsAlive);
    if (_playing && renderDeviceIsAlive == 1)
      {
        // We signal a stop for a shared device even when capturing has not
        // yet ended. This is to ensure the IOProc will return early as
        // intended (by checking |_playing|) before accessing resources we
        // free below (e.g. the render converter).
        //
        // In the case of a shared device, the IOProc will verify capturing
        // has ended before stopping itself.
        _playing = false;
        _doStop = true; // Signal to io proc to stop audio device
        _critSect.Leave(); // Cannot be under lock, risk of deadlock
        if (kEventTimeout == _stopEvent.Wait(2000))
          {
            CriticalSectionScoped critScoped(&_critSect);
            WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                         " Timed out stopping the render IOProc. "
                         "We may have failed to detect a device removal.");

            // We assume capturing on a shared device has stopped as well if the
            // IOProc times out.
            WEBRTC_CA_LOG_WARN(AudioDeviceStop(_outputDeviceID,
                                               _deviceIOProcID));
            WEBRTC_CA_LOG_WARN(AudioDeviceDestroyIOProcID(_outputDeviceID,
                                                          _deviceIOProcID));
          }
        _critSect.Enter();
        _doStop = false;
        WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id,
                     "Playout stopped");
      }

    // Setting this signal will allow the worker thread to be stopped.
    AtomicSet32(&_renderDeviceIsAlive, 0);
    if (render_worker_thread_.get()) {
      _critSect.Leave();
      render_worker_thread_->Stop();
      render_worker_thread_.reset();
      _critSect.Enter();
    }

    WEBRTC_CA_LOG_WARN(AudioConverterDispose(_renderConverter));

    // Remove listeners.
    AudioObjectPropertyAddress propertyAddress = {
      kAudioDevicePropertyStreamFormat, kAudioDevicePropertyScopeOutput,
      0 };
    WEBRTC_CA_LOG_WARN(AudioObjectRemovePropertyListener(_outputDeviceID,
                                                         &propertyAddress, &objectListenerProc, this));

    propertyAddress.mSelector = kAudioDeviceProcessorOverload;
    WEBRTC_CA_LOG_WARN(AudioObjectRemovePropertyListener(_outputDeviceID,
                                                         &propertyAddress, &objectListenerProc, this));

    if (_macBookPro)
      {
        Boolean hasProperty = AudioObjectHasProperty(_outputDeviceID,
                                                     &propertyAddress);
        if (hasProperty)
          {
            propertyAddress.mSelector = kAudioDevicePropertyDataSource;
            WEBRTC_CA_LOG_WARN(AudioObjectRemovePropertyListener(_outputDeviceID,
                                                                 &propertyAddress, &objectListenerProc, this));
          }
      }

    _playIsInitialized = false;
    _playing = false;

    return 0;
  }

  int32_t AudioDeviceMac::PlayoutDelay(uint16_t& delayMS) const
  {
    int32_t renderDelayUs = AtomicGet32(&_renderDelayUs);
    delayMS = static_cast<uint16_t> (1e-3 * (renderDelayUs + _renderLatencyUs) +
                                     0.5);
    return 0;
  }

  int32_t AudioDeviceMac::RecordingDelay(uint16_t& delayMS) const
  {
    int32_t captureDelayUs = AtomicGet32(&_captureDelayUs);
    delayMS = static_cast<uint16_t> (1e-3 * (captureDelayUs +
                                             _captureLatencyUs) + 0.5);
    return 0;
  }

  bool AudioDeviceMac::Playing() const
  {
    return (_playing);
  }

  int32_t AudioDeviceMac::SetPlayoutBuffer(
                                           const AudioDeviceModule::BufferType type,
                                           uint16_t sizeMS)
  {

    if (type != AudioDeviceModule::kFixedBufferSize)
      {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     " Adaptive buffer size not supported on this platform");
        return -1;
      }

    _playBufType = type;
    _playBufDelayFixed = sizeMS;
    return 0;
  }

  int32_t AudioDeviceMac::PlayoutBuffer(
                                        AudioDeviceModule::BufferType& type,
                                        uint16_t& sizeMS) const
  {

    type = _playBufType;
    sizeMS = _playBufDelayFixed;

    return 0;
  }

  // Not implemented for Mac.
  int32_t AudioDeviceMac::CPULoad(uint16_t& /*load*/) const
  {

    WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                 "  API call not supported on this platform");

    return -1;
  }

  bool AudioDeviceMac::PlayoutWarning() const
  {
    return (_playWarning > 0);
  }

  bool AudioDeviceMac::PlayoutError() const
  {
    return (_playError > 0);
  }

  bool AudioDeviceMac::RecordingWarning() const
  {
    return (_recWarning > 0);
  }

  bool AudioDeviceMac::RecordingError() const
  {
    return (_recError > 0);
  }

  void AudioDeviceMac::ClearPlayoutWarning()
  {
    _playWarning = 0;
  }

  void AudioDeviceMac::ClearPlayoutError()
  {
    _playError = 0;
  }

  void AudioDeviceMac::ClearRecordingWarning()
  {
    _recWarning = 0;
  }

  void AudioDeviceMac::ClearRecordingError()
  {
    _recError = 0;
  }

  // ============================================================================
  //                                 Private Methods
  // ============================================================================

  int32_t
  AudioDeviceMac::GetNumberDevices(const AudioObjectPropertyScope scope,
                                   AudioDeviceID scopedDeviceIds[],
                                   const uint32_t deviceListLength)
  {
    OSStatus err = noErr;

    AudioObjectPropertyAddress propertyAddress = {
      kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal,
      kAudioObjectPropertyElementMaster };
    UInt32 size = 0;
    WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
                                                           &propertyAddress, 0, NULL, &size));
    if (size == 0)
      {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "No devices");
        return 0;
      }

    AudioDeviceID* deviceIds = (AudioDeviceID*) malloc(size);
    UInt32 numberDevices = size / sizeof(AudioDeviceID);
    AudioBufferList* bufferList = NULL;
    UInt32 numberScopedDevices = 0;

    // First check if there is a default device and list it
    UInt32 hardwareProperty = 0;
    if (scope == kAudioDevicePropertyScopeOutput)
      {
        hardwareProperty = kAudioHardwarePropertyDefaultOutputDevice;
      } else
      {
        hardwareProperty = kAudioHardwarePropertyDefaultInputDevice;
      }

    AudioObjectPropertyAddress
      propertyAddressDefault = { hardwareProperty,
                                 kAudioObjectPropertyScopeGlobal,
                                 kAudioObjectPropertyElementMaster };

    AudioDeviceID usedID;
    UInt32 uintSize = sizeof(UInt32);
    WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(kAudioObjectSystemObject,
                                                       &propertyAddressDefault, 0, NULL, &uintSize, &usedID));
    if (usedID != kAudioDeviceUnknown)
      {
        scopedDeviceIds[numberScopedDevices] = usedID;
        numberScopedDevices++;
      } else
      {
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                     "GetNumberDevices(): Default device unknown");
      }

    // Then list the rest of the devices
    bool listOK = true;

    WEBRTC_CA_LOG_ERR(AudioObjectGetPropertyData(kAudioObjectSystemObject,
                                                 &propertyAddress, 0, NULL, &size, deviceIds));
    if (err != noErr)
      {
        listOK = false;
      } else
      {
        propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
        propertyAddress.mScope = scope;
        propertyAddress.mElement = 0;
        for (UInt32 i = 0; i < numberDevices; i++)
          {
            // Check for input channels
            WEBRTC_CA_LOG_ERR(AudioObjectGetPropertyDataSize(deviceIds[i],
                                                             &propertyAddress, 0, NULL, &size));
            if (err == kAudioHardwareBadDeviceError)
              {
                // This device doesn't actually exist; continue iterating.
                continue;
              } else if (err != noErr)
              {
                listOK = false;
                break;
              }

            bufferList = (AudioBufferList*) malloc(size);
            WEBRTC_CA_LOG_ERR(AudioObjectGetPropertyData(deviceIds[i],
                                                         &propertyAddress, 0, NULL, &size, bufferList));
            if (err != noErr)
              {
                listOK = false;
                break;
              }

            if (bufferList->mNumberBuffers > 0)
              {
                if (numberScopedDevices >= deviceListLength)
                  {
                    WEBRTC_TRACE(kTraceError,
                                 kTraceAudioDevice, _id,
                                 "Device list is not long enough");
                    listOK = false;
                    break;
                  }

                scopedDeviceIds[numberScopedDevices] = deviceIds[i];
                numberScopedDevices++;
              }

            free(bufferList);
            bufferList = NULL;
          }  // for
      }

    if (!listOK)
      {
        if (deviceIds)
          {
            free(deviceIds);
            deviceIds = NULL;
          }

        if (bufferList)
          {
            free(bufferList);
            bufferList = NULL;
          }

        return -1;
      }

    // Happy ending
    if (deviceIds)
      {
        free(deviceIds);
        deviceIds = NULL;
      }

    return numberScopedDevices;
  }

  int32_t
  AudioDeviceMac::GetDeviceName(const AudioObjectPropertyScope scope,
                                const uint16_t index,
                                char* name)
  {
    OSStatus err = noErr;
    UInt32 len = kAdmMaxDeviceNameSize;
    AudioDeviceID deviceIds[MaxNumberDevices];

    int numberDevices = GetNumberDevices(scope, deviceIds, MaxNumberDevices);
    if (numberDevices < 0)
      {
        return -1;
      } else if (numberDevices == 0)
      {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "No devices");
        return -1;
      }

    // If the number is below the number of devices, assume it's "WEBRTC ID"
    // otherwise assume it's a CoreAudio ID
    AudioDeviceID usedID;

    // Check if there is a default device
    bool isDefaultDevice = false;
    if (index == 0)
      {
        UInt32 hardwareProperty = 0;
        if (scope == kAudioDevicePropertyScopeOutput)
          {
            hardwareProperty = kAudioHardwarePropertyDefaultOutputDevice;
          } else
          {
            hardwareProperty = kAudioHardwarePropertyDefaultInputDevice;
          }
        AudioObjectPropertyAddress propertyAddress = { hardwareProperty,
                                                       kAudioObjectPropertyScopeGlobal,
                                                       kAudioObjectPropertyElementMaster };
        UInt32 size = sizeof(UInt32);
        WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(kAudioObjectSystemObject,
                                                           &propertyAddress, 0, NULL, &size, &usedID));
        if (usedID == kAudioDeviceUnknown)
          {
            WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                         "GetDeviceName(): Default device unknown");
          } else
          {
            isDefaultDevice = true;
          }
      }

    AudioObjectPropertyAddress propertyAddress = {
      kAudioDevicePropertyDeviceName, scope, 0 };

    if (isDefaultDevice)
      {
        char devName[len];

        WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(usedID,
                                                           &propertyAddress, 0, NULL, &len, devName));

        sprintf(name, "default (%s)", devName);
      } else
      {
        if (index < numberDevices)
          {
            usedID = deviceIds[index];
          } else
          {
            usedID = index;
          }

        WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(usedID,
                                                           &propertyAddress, 0, NULL, &len, name));
      }

    return 0;
  }

  int32_t AudioDeviceMac::InitDevice(const uint16_t userDeviceIndex,
                                     AudioDeviceID& deviceId,
                                     const bool isInput)
  {
    OSStatus err = noErr;
    UInt32 size = 0;
    AudioObjectPropertyScope deviceScope;
    AudioObjectPropertySelector defaultDeviceSelector;
    AudioDeviceID deviceIds[MaxNumberDevices];

    if (isInput)
      {
        deviceScope = kAudioDevicePropertyScopeInput;
        defaultDeviceSelector = kAudioHardwarePropertyDefaultInputDevice;
      } else
      {
        deviceScope = kAudioDevicePropertyScopeOutput;
        defaultDeviceSelector = kAudioHardwarePropertyDefaultOutputDevice;
      }

    AudioObjectPropertyAddress
      propertyAddress = { defaultDeviceSelector,
                          kAudioObjectPropertyScopeGlobal,
                          kAudioObjectPropertyElementMaster };

    // Get the actual device IDs
    int numberDevices = GetNumberDevices(deviceScope, deviceIds,
                                         MaxNumberDevices);
    if (numberDevices < 0)
      {
        return -1;
      } else if (numberDevices == 0)
      {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "InitDevice(): No devices");
        return -1;
      }

    bool isDefaultDevice = false;
    deviceId = kAudioDeviceUnknown;
    if (userDeviceIndex == 0)
      {
        // Try to use default system device
        size = sizeof(AudioDeviceID);
        WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(kAudioObjectSystemObject,
                                                           &propertyAddress, 0, NULL, &size, &deviceId));
        if (deviceId == kAudioDeviceUnknown)
          {
            WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                         " No default device exists");
          } else
          {
            isDefaultDevice = true;
          }
      }

    if (!isDefaultDevice)
      {
        deviceId = deviceIds[userDeviceIndex];
      }

    // Obtain device name and manufacturer for logging.
    // Also use this as a test to ensure a user-set device ID is valid.
    char devName[128];
    char devManf[128];
    memset(devName, 0, sizeof(devName));
    memset(devManf, 0, sizeof(devManf));

    propertyAddress.mSelector = kAudioDevicePropertyDeviceName;
    propertyAddress.mScope = deviceScope;
    propertyAddress.mElement = 0;
    size = sizeof(devName);
    WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(deviceId,
                                                       &propertyAddress, 0, NULL, &size, devName));

    propertyAddress.mSelector = kAudioDevicePropertyDeviceManufacturer;
    size = sizeof(devManf);
    WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(deviceId,
                                                       &propertyAddress, 0, NULL, &size, devManf));

    if (isInput)
      {
        WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                     " Input device: %s %s", devManf, devName);
      } else
      {
        WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                     " Output device: %s %s", devManf, devName);
      }

    return 0;
  }

  OSStatus AudioDeviceMac::SetDesiredPlayoutFormat()
  {
    // Our preferred format to work with.
    _outDesiredFormat.mSampleRate = N_PLAY_SAMPLES_PER_SEC;
    _outDesiredFormat.mChannelsPerFrame = _playChannels;

    if (_ptrAudioBuffer)
      {
        // Update audio buffer with the selected parameters.
        _ptrAudioBuffer->SetPlayoutSampleRate(N_PLAY_SAMPLES_PER_SEC);
        _ptrAudioBuffer->SetPlayoutChannels((uint8_t) _playChannels);
      }

    _renderDelayOffsetSamples = _renderBufSizeSamples - N_BUFFERS_OUT *
      ENGINE_PLAY_BUF_SIZE_IN_SAMPLES * _outDesiredFormat.mChannelsPerFrame;

    _outDesiredFormat.mBytesPerPacket = _outDesiredFormat.mChannelsPerFrame *
      sizeof(SInt16);
    // In uncompressed audio, a packet is one frame.
    _outDesiredFormat.mFramesPerPacket = 1;
    _outDesiredFormat.mBytesPerFrame = _outDesiredFormat.mChannelsPerFrame *
      sizeof(SInt16);
    _outDesiredFormat.mBitsPerChannel = sizeof(SInt16) * 8;

    _outDesiredFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger |
      kLinearPCMFormatFlagIsPacked;
#ifdef WEBRTC_ARCH_BIG_ENDIAN
    _outDesiredFormat.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
#endif
    _outDesiredFormat.mFormatID = kAudioFormatLinearPCM;

    OSStatus err = noErr;
    WEBRTC_CA_RETURN_ON_ERR(AudioConverterNew(&_outDesiredFormat,
                                              &_outStreamFormat,
                                              &_renderConverter));

    // Try to set buffer size to desired value (_playBufDelayFixed).
    UInt32 bufByteCount = static_cast<UInt32> ((_outStreamFormat.mSampleRate /
                                                1000.0) *
                                               _playBufDelayFixed *
                                               _outStreamFormat.mChannelsPerFrame *
                                               sizeof(Float32));
    if (_outStreamFormat.mFramesPerPacket != 0)
      {
        if (bufByteCount % _outStreamFormat.mFramesPerPacket != 0)
          {
            bufByteCount = (static_cast<UInt32> (bufByteCount /
                                                 _outStreamFormat.mFramesPerPacket) + 1) *
              _outStreamFormat.mFramesPerPacket;
          }
      }

    // Ensure the buffer size is within the range provided by the device.
    AudioObjectPropertyAddress propertyAddress =
      {kAudioDevicePropertyDataSource,
       kAudioDevicePropertyScopeOutput,
       0};
    propertyAddress.mSelector = kAudioDevicePropertyBufferSizeRange;
    AudioValueRange range;
    UInt32 size = sizeof(range);
    WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(_outputDeviceID,
                                                       &propertyAddress,
                                                       0,
                                                       NULL,
                                                       &size,
                                                       &range));
    if (range.mMinimum > bufByteCount)
      {
        bufByteCount = range.mMinimum;
      } else if (range.mMaximum < bufByteCount)
      {
        bufByteCount = range.mMaximum;
      }

    propertyAddress.mSelector = kAudioDevicePropertyBufferSize;
    size = sizeof(bufByteCount);
    WEBRTC_CA_RETURN_ON_ERR(AudioObjectSetPropertyData(_outputDeviceID,
                                                       &propertyAddress,
                                                       0,
                                                       NULL,
                                                       size,
                                                       &bufByteCount));

    // Get render device latency.
    propertyAddress.mSelector = kAudioDevicePropertyLatency;
    UInt32 latency = 0;
    size = sizeof(UInt32);
    WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(_outputDeviceID,
                                                       &propertyAddress,
                                                       0,
                                                       NULL,
                                                       &size,
                                                       &latency));
    _renderLatencyUs = static_cast<uint32_t> ((1.0e6 * latency) /
                                              _outStreamFormat.mSampleRate);

    // Get render stream latency.
    propertyAddress.mSelector = kAudioDevicePropertyStreams;
    AudioStreamID stream = 0;
    size = sizeof(AudioStreamID);
    WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(_outputDeviceID,
                                                       &propertyAddress,
                                                       0,
                                                       NULL,
                                                       &size,
                                                       &stream));
    propertyAddress.mSelector = kAudioStreamPropertyLatency;
    size = sizeof(UInt32);
    latency = 0;
    WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(_outputDeviceID,
                                                       &propertyAddress,
                                                       0,
                                                       NULL,
                                                       &size,
                                                       &latency));
    _renderLatencyUs += static_cast<uint32_t> ((1.0e6 * latency) /
                                               _outStreamFormat.mSampleRate);

    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "  initial playout status: _renderDelayOffsetSamples=%d,"
                 " _renderDelayUs=%d, _renderLatencyUs=%d",
                 _renderDelayOffsetSamples, _renderDelayUs, _renderLatencyUs);
    return 0;
  }

  OSStatus AudioDeviceMac::objectListenerProc(
                                              AudioObjectID objectId,
                                              UInt32 numberAddresses,
                                              const AudioObjectPropertyAddress addresses[],
                                              void* clientData)
  {
    AudioDeviceMac *ptrThis = (AudioDeviceMac *) clientData;
    RTC_DCHECK(ptrThis != NULL);

    ptrThis->implObjectListenerProc(objectId, numberAddresses, addresses);

    // AudioObjectPropertyListenerProc functions are supposed to return 0
    return 0;
  }

  OSStatus AudioDeviceMac::implObjectListenerProc(
                                                  const AudioObjectID objectId,
                                                  const UInt32 numberAddresses,
                                                  const AudioObjectPropertyAddress addresses[])
  {
    WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id,
                 "AudioDeviceMac::implObjectListenerProc()");

    for (UInt32 i = 0; i < numberAddresses; i++)
      {
        if (addresses[i].mSelector == kAudioHardwarePropertyDevices)
          {
            HandleDeviceChange();
          } else if (addresses[i].mSelector == kAudioDevicePropertyStreamFormat)
          {
            HandleStreamFormatChange(objectId, addresses[i]);
          } else if (addresses[i].mSelector == kAudioDevicePropertyDataSource)
          {
            HandleDataSourceChange(objectId, addresses[i]);
          } else if (addresses[i].mSelector == kAudioDeviceProcessorOverload)
          {
            HandleProcessorOverload(addresses[i]);
          }
      }

    return 0;
  }

  int32_t AudioDeviceMac::HandleDeviceChange()
  {
    OSStatus err = noErr;

    WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id,
                 "kAudioHardwarePropertyDevices");

    // A device has changed. Check if our registered devices have been removed.
    // Ensure the devices have been initialized, meaning the IDs are valid.
    if (MicrophoneIsInitialized())
      {
        AudioObjectPropertyAddress propertyAddress = {
          kAudioDevicePropertyDeviceIsAlive,
          kAudioDevicePropertyScopeInput, 0 };
        UInt32 deviceIsAlive = 1;
        UInt32 size = sizeof(UInt32);
        err = AudioObjectGetPropertyData(_inputDeviceID, &propertyAddress, 0,
                                         NULL, &size, &deviceIsAlive);

        if (err == kAudioHardwareBadDeviceError || deviceIsAlive == 0)
          {
            WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                         "Capture device is not alive (probably removed)");
            AtomicSet32(&_captureDeviceIsAlive, 0);
            _mixerManager.CloseMicrophone();
            if (_recError == 1)
              {
                WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice,
                             _id, "  pending recording error exists");
              }
            _recError = 1; // triggers callback from module process thread
          } else if (err != noErr)
          {
            logCAMsg(kTraceError, kTraceAudioDevice, _id,
                     "Error in AudioDeviceGetProperty()", (const char*) &err);
            return -1;
          }
      }

    if (SpeakerIsInitialized())
      {
        AudioObjectPropertyAddress propertyAddress = {
          kAudioDevicePropertyDeviceIsAlive,
          kAudioDevicePropertyScopeOutput, 0 };
        UInt32 deviceIsAlive = 1;
        UInt32 size = sizeof(UInt32);
        err = AudioObjectGetPropertyData(_outputDeviceID, &propertyAddress, 0,
                                         NULL, &size, &deviceIsAlive);

        if (err == kAudioHardwareBadDeviceError || deviceIsAlive == 0)
          {
            WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                         "Render device is not alive (probably removed)");
            AtomicSet32(&_renderDeviceIsAlive, 0);
            _mixerManager.CloseSpeaker();
            if (_playError == 1)
              {
                WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice,
                             _id, "  pending playout error exists");
              }
            _playError = 1; // triggers callback from module process thread
          } else if (err != noErr)
          {
            logCAMsg(kTraceError, kTraceAudioDevice, _id,
                     "Error in AudioDeviceGetProperty()", (const char*) &err);
            return -1;
          }
      }

    return 0;
  }

  int32_t AudioDeviceMac::HandleStreamFormatChange(
                                                   const AudioObjectID objectId,
                                                   const AudioObjectPropertyAddress propertyAddress)
  {
    OSStatus err = noErr;

    WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id,
                 "Stream format changed");

    if (objectId != _inputDeviceID && objectId != _outputDeviceID)
      {
        return 0;
      }

    // Get the new device format
    AudioStreamBasicDescription streamFormat;
    UInt32 size = sizeof(streamFormat);
    WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(objectId,
                                                       &propertyAddress, 0, NULL, &size, &streamFormat));

    if (streamFormat.mFormatID != kAudioFormatLinearPCM)
      {
        logCAMsg(kTraceError, kTraceAudioDevice, _id,
                 "Unacceptable input stream format -> mFormatID",
                 (const char *) &streamFormat.mFormatID);
        return -1;
      }

    if (streamFormat.mChannelsPerFrame > N_DEVICE_CHANNELS)
      {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "Too many channels on device (mChannelsPerFrame = %d)",
                     streamFormat.mChannelsPerFrame);
        return -1;
      }

    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "Stream format:");
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "mSampleRate = %f, mChannelsPerFrame = %u",
                 streamFormat.mSampleRate, streamFormat.mChannelsPerFrame);
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "mBytesPerPacket = %u, mFramesPerPacket = %u",
                 streamFormat.mBytesPerPacket, streamFormat.mFramesPerPacket);
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "mBytesPerFrame = %u, mBitsPerChannel = %u",
                 streamFormat.mBytesPerFrame, streamFormat.mBitsPerChannel);
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "mFormatFlags = %u",
                 streamFormat.mFormatFlags);
    logCAMsg(kTraceInfo, kTraceAudioDevice, _id, "mFormatID",
             (const char *) &streamFormat.mFormatID);

    if (propertyAddress.mScope == kAudioDevicePropertyScopeInput)
      {
        const int io_block_size_samples = streamFormat.mChannelsPerFrame *
          streamFormat.mSampleRate / 100 * N_BLOCKS_IO;
        if (io_block_size_samples > _captureBufSizeSamples)
          {
            WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                         "Input IO block size (%d) is larger than ring buffer (%u)",
                         io_block_size_samples, _captureBufSizeSamples);
            return -1;

          }

        memcpy(&_inStreamFormat, &streamFormat, sizeof(streamFormat));

        if (_inStreamFormat.mChannelsPerFrame >= 2 && (_recChannels == 2))
          {
            _inDesiredFormat.mChannelsPerFrame = 2;
          } else
          {
            // Disable stereo recording when we only have one channel on the device.
            _inDesiredFormat.mChannelsPerFrame = 1;
            _recChannels = 1;
            WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                         "Stereo recording unavailable on this device");
          }

        if (_ptrAudioBuffer)
          {
            // Update audio buffer with the selected parameters
            _ptrAudioBuffer->SetRecordingSampleRate(N_REC_SAMPLES_PER_SEC);
            _ptrAudioBuffer->SetRecordingChannels((uint8_t) _recChannels);
          }

        // Recreate the converter with the new format
        // TODO(xians): make this thread safe
        WEBRTC_CA_RETURN_ON_ERR(AudioConverterDispose(_captureConverter));

        WEBRTC_CA_RETURN_ON_ERR(AudioConverterNew(&streamFormat, &_inDesiredFormat,
                                                  &_captureConverter));
      } else
      {
        memcpy(&_outStreamFormat, &streamFormat, sizeof(streamFormat));

        // Our preferred format to work with
        if (_outStreamFormat.mChannelsPerFrame < 2)
          {
            _playChannels = 1;
            WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                         "Stereo playout unavailable on this device");
          }
        WEBRTC_CA_RETURN_ON_ERR(SetDesiredPlayoutFormat());
      }
    return 0;
  }

  int32_t AudioDeviceMac::HandleDataSourceChange(
                                                 const AudioObjectID objectId,
                                                 const AudioObjectPropertyAddress propertyAddress)
  {
    OSStatus err = noErr;

    if (_macBookPro && propertyAddress.mScope
        == kAudioDevicePropertyScopeOutput)
      {
        WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id,
                     "Data source changed");

        _macBookProPanRight = false;
        UInt32 dataSource = 0;
        UInt32 size = sizeof(UInt32);
        WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(objectId,
                                                           &propertyAddress, 0, NULL, &size, &dataSource));
        if (dataSource == 'ispk')
          {
            _macBookProPanRight = true;
            WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                         "MacBook Pro using internal speakers; stereo panning right");
          } else
          {
            WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                         "MacBook Pro not using internal speakers");
          }
      }

    return 0;
  }
  int32_t AudioDeviceMac::HandleProcessorOverload(
                                                  const AudioObjectPropertyAddress propertyAddress)
  {
    // TODO(xians): we probably want to notify the user in some way of the
    // overload. However, the Windows interpretations of these errors seem to
    // be more severe than what ProcessorOverload is thrown for.
    //
    // We don't log the notification, as it's sent from the HAL's IO thread. We
    // don't want to slow it down even further.
    if (propertyAddress.mScope == kAudioDevicePropertyScopeInput)
      {
        //WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "Capture processor
        // overload");
        //_callback->ProblemIsReported(
        // SndCardStreamObserver::ERecordingProblem);
      } else
      {
        //WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
        // "Render processor overload");
        //_callback->ProblemIsReported(
        // SndCardStreamObserver::EPlaybackProblem);
      }

    return 0;
  }

  // ============================================================================
  //                                  Thread Methods
  // ============================================================================

  OSStatus AudioDeviceMac::deviceIOProc(AudioDeviceID, const AudioTimeStamp*,
                                        const AudioBufferList* inputData,
                                        const AudioTimeStamp* inputTime,
                                        AudioBufferList* outputData,
                                        const AudioTimeStamp* outputTime,
                                        void *clientData)
  {
    AudioDeviceMac *ptrThis = (AudioDeviceMac *) clientData;
    RTC_DCHECK(ptrThis != NULL);

    ptrThis->implDeviceIOProc(inputData, inputTime, outputData, outputTime);

    // AudioDeviceIOProc functions are supposed to return 0
    return 0;
  }

  OSStatus AudioDeviceMac::outConverterProc(AudioConverterRef,
                                            UInt32 *numberDataPackets,
                                            AudioBufferList *data,
                                            AudioStreamPacketDescription **,
                                            void *userData)
  {
    AudioDeviceMac *ptrThis = (AudioDeviceMac *) userData;
    RTC_DCHECK(ptrThis != NULL);

    return ptrThis->implOutConverterProc(numberDataPackets, data);
  }

  OSStatus AudioDeviceMac::inDeviceIOProc(AudioDeviceID, const AudioTimeStamp*,
                                          const AudioBufferList* inputData,
                                          const AudioTimeStamp* inputTime,
                                          AudioBufferList*,
                                          const AudioTimeStamp*, void* clientData)
  {
    AudioDeviceMac *ptrThis = (AudioDeviceMac *) clientData;
    RTC_DCHECK(ptrThis != NULL);

    ptrThis->implInDeviceIOProc(inputData, inputTime);

    // AudioDeviceIOProc functions are supposed to return 0
    return 0;
  }

  OSStatus AudioDeviceMac::inConverterProc(
                                           AudioConverterRef,
                                           UInt32 *numberDataPackets,
                                           AudioBufferList *data,
                                           AudioStreamPacketDescription ** /*dataPacketDescription*/,
                                           void *userData)
  {
    AudioDeviceMac *ptrThis = static_cast<AudioDeviceMac*> (userData);
    printf("in converter proc\n");
    RTC_DCHECK(ptrThis != NULL);

    return ptrThis->implInConverterProc(numberDataPackets, data);
  }

  OSStatus AudioDeviceMac::implDeviceIOProc(const AudioBufferList *inputData,
                                            const AudioTimeStamp *inputTime,
                                            AudioBufferList *outputData,
                                            const AudioTimeStamp *outputTime)
  {
    OSStatus err = noErr;
    UInt64 outputTimeNs = AudioConvertHostTimeToNanos(outputTime->mHostTime);
    UInt64 nowNs = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());

    if (!_twoDevices && _recording)
      {
        implInDeviceIOProc(inputData, inputTime);
      }

    // Check if we should close down audio device
    // Double-checked locking optimization to remove locking overhead
    if (_doStop)
      {
        _critSect.Enter();
        if (_doStop)
          {
            if (_twoDevices || (!_recording && !_playing))
              {
                // In the case of a shared device, the single driving ioProc
                // is stopped here
                WEBRTC_CA_LOG_ERR(AudioDeviceStop(_outputDeviceID,
                                                  _deviceIOProcID));
                WEBRTC_CA_LOG_WARN(AudioDeviceDestroyIOProcID(_outputDeviceID,
                                                              _deviceIOProcID));
                if (err == noErr)
                  {
                    WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice,
                                 _id, " Playout or shared device stopped");
                  }
              }

            _doStop = false;
            _stopEvent.Set();
            _critSect.Leave();
            return 0;
          }
        _critSect.Leave();
      }

    if (!_playing)
      {
        // This can be the case when a shared device is capturing but not
        // rendering. We allow the checks above before returning to avoid a
        // timeout when capturing is stopped.
        return 0;
      }

    RTC_DCHECK(_outStreamFormat.mBytesPerFrame != 0);
    UInt32 size = outputData->mBuffers->mDataByteSize
      / _outStreamFormat.mBytesPerFrame;

    // TODO(xians): signal an error somehow?
    err = AudioConverterFillComplexBuffer(_renderConverter, outConverterProc,
                                          this, &size, outputData, NULL);
    if (err != noErr)
      {
        if (err == 1)
          {
            // This is our own error.
            WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                         " Error in AudioConverterFillComplexBuffer()");
            return 1;
          } else
          {
            logCAMsg(kTraceError, kTraceAudioDevice, _id,
                     "Error in AudioConverterFillComplexBuffer()",
                     (const char *) &err);
            return 1;
          }
      }

    PaRingBufferSize bufSizeSamples =
      PaUtil_GetRingBufferReadAvailable(_paRenderBuffer);

    int32_t renderDelayUs = static_cast<int32_t> (1e-3 * (outputTimeNs - nowNs)
                                                  + 0.5);
    renderDelayUs += static_cast<int32_t> ((1.0e6 * bufSizeSamples)
                                           / _outDesiredFormat.mChannelsPerFrame / _outDesiredFormat.mSampleRate
                                           + 0.5);

    AtomicSet32(&_renderDelayUs, renderDelayUs);

    return 0;
  }

  OSStatus AudioDeviceMac::implOutConverterProc(UInt32 *numberDataPackets,
                                                AudioBufferList *data)
  {
    RTC_DCHECK(data->mNumberBuffers == 1);
    PaRingBufferSize numSamples = *numberDataPackets
      * _outDesiredFormat.mChannelsPerFrame;

    data->mBuffers->mNumberChannels = _outDesiredFormat.mChannelsPerFrame;
    // Always give the converter as much as it wants, zero padding as required.
    data->mBuffers->mDataByteSize = *numberDataPackets
      * _outDesiredFormat.mBytesPerPacket;
    data->mBuffers->mData = _renderConvertData;
    memset(_renderConvertData, 0, sizeof(_renderConvertData));

    PaUtil_ReadRingBuffer(_paRenderBuffer, _renderConvertData, numSamples);

    kern_return_t kernErr = semaphore_signal_all(_renderSemaphore);
    if (kernErr != KERN_SUCCESS)
      {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     " semaphore_signal_all() error: %d", kernErr);
        return 1;
      }

    return 0;
  }

  OSStatus AudioDeviceMac::implInDeviceIOProc(const AudioBufferList *inputData,
                                              const AudioTimeStamp *inputTime)
  {
    OSStatus err = noErr;
    UInt64 inputTimeNs = AudioConvertHostTimeToNanos(inputTime->mHostTime);
    UInt64 nowNs = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());

    // Check if we should close down audio device
    // Double-checked locking optimization to remove locking overhead
    if (_doStopRec)
      {
        _critSect.Enter();
        if (_doStopRec)
          {
            // This will be signalled only when a shared device is not in use.
            WEBRTC_CA_LOG_ERR(AudioDeviceStop(_inputDeviceID, _inDeviceIOProcID));
            WEBRTC_CA_LOG_WARN(AudioDeviceDestroyIOProcID(_inputDeviceID,
                                                          _inDeviceIOProcID));
            if (err == noErr)
              {
                WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice,
                             _id, " Recording device stopped");
              }

            _doStopRec = false;
            _stopEventRec.Set();
            _critSect.Leave();
            return 0;
          }
        _critSect.Leave();
      }

    if (!_recording)
      {
        // Allow above checks to avoid a timeout on stopping capture.
        return 0;
      }

    PaRingBufferSize bufSizeSamples =
      PaUtil_GetRingBufferReadAvailable(_paCaptureBuffer);

    int32_t captureDelayUs = static_cast<int32_t> (1e-3 * (nowNs - inputTimeNs)
                                                   + 0.5);
    captureDelayUs
      += static_cast<int32_t> ((1.0e6 * bufSizeSamples)
                               / _inStreamFormat.mChannelsPerFrame / _inStreamFormat.mSampleRate
                               + 0.5);

    AtomicSet32(&_captureDelayUs, captureDelayUs);

    RTC_DCHECK(inputData->mNumberBuffers == 1);
    PaRingBufferSize numSamples = inputData->mBuffers->mDataByteSize
      * _inStreamFormat.mChannelsPerFrame / _inStreamFormat.mBytesPerPacket;
    PaUtil_WriteRingBuffer(_paCaptureBuffer, inputData->mBuffers->mData,
                           numSamples);

    kern_return_t kernErr = semaphore_signal_all(_captureSemaphore);
    if (kernErr != KERN_SUCCESS)
      {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     " semaphore_signal_all() error: %d", kernErr);
      }

    return err;
  }

  OSStatus AudioDeviceMac::implInConverterProc(UInt32 *numberDataPackets,
                                               AudioBufferList *data)
  {
    RTC_DCHECK(data->mNumberBuffers == 1);
    PaRingBufferSize numSamples = *numberDataPackets
      * _inStreamFormat.mChannelsPerFrame;

    while (PaUtil_GetRingBufferReadAvailable(_paCaptureBuffer) < numSamples)
      {
        mach_timespec_t timeout;
        timeout.tv_sec = 0;
        timeout.tv_nsec = TIMER_PERIOD_MS;

        kern_return_t kernErr = semaphore_timedwait(_captureSemaphore, timeout);
        if (kernErr == KERN_OPERATION_TIMED_OUT)
          {
            int32_t signal = AtomicGet32(&_captureDeviceIsAlive);
            if (signal == 0)
              {
                // The capture device is no longer alive; stop the worker thread.
                *numberDataPackets = 0;
                return 1;
              }
          } else if (kernErr != KERN_SUCCESS)
          {
            WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                         " semaphore_wait() error: %d", kernErr);
          }
      }

    // Pass the read pointer directly to the converter to avoid a memcpy.
    void* dummyPtr;
    PaRingBufferSize dummySize;
    PaUtil_GetRingBufferReadRegions(_paCaptureBuffer, numSamples,
                                    &data->mBuffers->mData, &numSamples,
                                    &dummyPtr, &dummySize);
    PaUtil_AdvanceRingBufferReadIndex(_paCaptureBuffer, numSamples);

    data->mBuffers->mNumberChannels = _inStreamFormat.mChannelsPerFrame;
    *numberDataPackets = numSamples / _inStreamFormat.mChannelsPerFrame;
    data->mBuffers->mDataByteSize = *numberDataPackets
      * _inStreamFormat.mBytesPerPacket;

    return 0;
  }

  bool AudioDeviceMac::RunRender(void* ptrThis)
  {
    return static_cast<AudioDeviceMac*> (ptrThis)->RenderWorkerThread();
  }

  bool AudioDeviceMac::RenderWorkerThread()
  {
    PaRingBufferSize numSamples = ENGINE_PLAY_BUF_SIZE_IN_SAMPLES
      * _outDesiredFormat.mChannelsPerFrame;
    while (PaUtil_GetRingBufferWriteAvailable(_paRenderBuffer)
           - _renderDelayOffsetSamples < numSamples)
      {
        mach_timespec_t timeout;
        timeout.tv_sec = 0;
        timeout.tv_nsec = TIMER_PERIOD_MS;

        kern_return_t kernErr = semaphore_timedwait(_renderSemaphore, timeout);
        if (kernErr == KERN_OPERATION_TIMED_OUT)
          {
            int32_t signal = AtomicGet32(&_renderDeviceIsAlive);
            if (signal == 0)
              {
                // The render device is no longer alive; stop the worker thread.
                return false;
              }
          } else if (kernErr != KERN_SUCCESS)
          {
            WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                         " semaphore_timedwait() error: %d", kernErr);
          }
      }

    int8_t playBuffer[4 * ENGINE_PLAY_BUF_SIZE_IN_SAMPLES];

    if (!_ptrAudioBuffer)
      {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "  capture AudioBuffer is invalid");
        return false;
      }

    // Ask for new PCM data to be played out using the AudioDeviceBuffer.
    uint32_t nSamples =
      _ptrAudioBuffer->RequestPlayoutData(ENGINE_PLAY_BUF_SIZE_IN_SAMPLES);

    nSamples = _ptrAudioBuffer->GetPlayoutData(playBuffer);
    if (nSamples != ENGINE_PLAY_BUF_SIZE_IN_SAMPLES)
      {
        WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                     "  invalid number of output samples(%d)", nSamples);
      }

    uint32_t nOutSamples = nSamples * _outDesiredFormat.mChannelsPerFrame;

    SInt16 *pPlayBuffer = (SInt16 *) &playBuffer;
    if (_macBookProPanRight && (_playChannels == 2))
      {
        // Mix entirely into the right channel and zero the left channel.
        SInt32 sampleInt32 = 0;
        for (uint32_t sampleIdx = 0; sampleIdx < nOutSamples; sampleIdx
               += 2)
          {
            sampleInt32 = pPlayBuffer[sampleIdx];
            sampleInt32 += pPlayBuffer[sampleIdx + 1];
            sampleInt32 /= 2;

            if (sampleInt32 > 32767)
              {
                sampleInt32 = 32767;
              } else if (sampleInt32 < -32768)
              {
                sampleInt32 = -32768;
              }

            pPlayBuffer[sampleIdx] = 0;
            pPlayBuffer[sampleIdx + 1] = static_cast<SInt16> (sampleInt32);
          }
      }

    PaUtil_WriteRingBuffer(_paRenderBuffer, pPlayBuffer, nOutSamples);

    return true;
  }

  bool AudioDeviceMac::RunCapture(void* ptrThis)
  {
    return static_cast<AudioDeviceMac*> (ptrThis)->CaptureWorkerThread();
  }

  bool AudioDeviceMac::CaptureWorkerThread()
  {
    OSStatus err = noErr;
    UInt32 noRecSamples = ENGINE_REC_BUF_SIZE_IN_SAMPLES
      * _inDesiredFormat.mChannelsPerFrame;
    SInt16 recordBuffer[noRecSamples];
    UInt32 size = ENGINE_REC_BUF_SIZE_IN_SAMPLES;
    printf("capture worker thread\n");
    AudioBufferList engineBuffer;
    engineBuffer.mNumberBuffers = 1; // Interleaved channels.
    engineBuffer.mBuffers->mNumberChannels = _inDesiredFormat.mChannelsPerFrame;
    engineBuffer.mBuffers->mDataByteSize = _inDesiredFormat.mBytesPerPacket
      * noRecSamples;
    engineBuffer.mBuffers->mData = recordBuffer;

    err = AudioConverterFillComplexBuffer(_captureConverter, inConverterProc,
                                          this, &size, &engineBuffer, NULL);
    printf("finished fill complex buffer, err:%d\n",err);
    if (err != noErr)
      {
        if (err == 1)
          {
            // This is our own error.
            return false;
          } else
          {
            logCAMsg(kTraceError, kTraceAudioDevice, _id,
                     "Error in AudioConverterFillComplexBuffer()",
                     (const char *) &err);
            return false;
          }
      }

    // TODO(xians): what if the returned size is incorrect?
    if (size == ENGINE_REC_BUF_SIZE_IN_SAMPLES)
      {
        printf("return size is correct\n");
        uint32_t currentMicLevel(0);
        uint32_t newMicLevel(0);
        int32_t msecOnPlaySide;
        int32_t msecOnRecordSide;

        int32_t captureDelayUs = AtomicGet32(&_captureDelayUs);
        int32_t renderDelayUs = AtomicGet32(&_renderDelayUs);

        msecOnPlaySide = static_cast<int32_t> (1e-3 * (renderDelayUs +
                                                       _renderLatencyUs) + 0.5);
        msecOnRecordSide = static_cast<int32_t> (1e-3 * (captureDelayUs +
                                                         _captureLatencyUs) +
                                                 0.5);

        if (!_ptrAudioBuffer)
          {
            WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                         "  capture AudioBuffer is invalid");
            return false;
          }

        // store the recorded buffer (no action will be taken if the
        // #recorded samples is not a full buffer)
        _ptrAudioBuffer->SetRecordedBuffer((int8_t*) &recordBuffer,
                                           (uint32_t) size);
        printf("set record buffer\n");
        if (AGC())
          {
            // store current mic level in the audio buffer if AGC is enabled
            if (MicrophoneVolume(currentMicLevel) == 0)
              {
                // this call does not affect the actual microphone volume
                _ptrAudioBuffer->SetCurrentMicLevel(currentMicLevel);
              }
          }

        _ptrAudioBuffer->SetVQEData(msecOnPlaySide, msecOnRecordSide, 0);

        _ptrAudioBuffer->SetTypingStatus(KeyPressed());

        // deliver recorded samples at specified sample rate, mic level etc.
        // to the observer using callback
        _ptrAudioBuffer->DeliverRecordedData();

        if (AGC())
          {
            printf("AGC enabled\n");
            newMicLevel = _ptrAudioBuffer->NewMicLevel();
            if (newMicLevel != 0)
              {
                // The VQE will only deliver non-zero microphone levels when
                // a change is needed.
                // Set this new mic level (received from the observer as return
                // value in the callback).
                WEBRTC_TRACE(kTraceStream, kTraceAudioDevice,
                             _id, "  AGC change of volume: old=%u => new=%u",
                             currentMicLevel, newMicLevel);
                if (SetMicrophoneVolume(newMicLevel) == -1)
                  {
                    WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                                 "  the required modification of the microphone "
                                 "volume failed");
                  }
              }
          }
      }

    return true;
  }

  bool AudioDeviceMac::KeyPressed() {
    bool key_down = false;
    // Loop through all Mac virtual key constant values.
    for (unsigned int key_index = 0;
         key_index < arraysize(prev_key_state_);
         ++key_index) {
      bool keyState = CGEventSourceKeyState(
                                            kCGEventSourceStateHIDSystemState,
                                            key_index);
      // A false -> true change in keymap means a key is pressed.
      key_down |= (keyState && !prev_key_state_[key_index]);
      // Save current state.
      prev_key_state_[key_index] = keyState;
    }
    return key_down;
  }
}  // namespace webrtc
